package com.alogic.pipeline.impl;

import com.alogic.pipeline.PipelineListener;
import com.alogic.pipeline.PipelineStage;
import com.alogic.pipeline.StageContext;
import com.alogic.xscript.*;
import com.alogic.xscript.doc.XsObject;
import com.alogic.xscript.doc.json.JsonObject;
import com.alogic.xscript.plugins.Segment;
import com.anysoft.util.*;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.w3c.dom.Element;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

/**
 * StepStage
 *
 * @since 1.6.13.37 [20200223 duanyy] <br>
 *
 * @version 1.6.13.39 [20210225 duanyy] <br>
 * - 增加on-prepare事件支持；
 *
 * @version 1.6.13.39 [20210225 duanyy] <br>
 * - 增加on-prepare事件支持；
 * - step可以取消；
 */
public class StepStage2 extends PipelineStage.Base {
    protected Logiclet script = null;
    protected PipelineStage theStep = null;
    protected final static String ID = "$stageContexts";
    protected Logiclet onPrepare = null;
    protected Logiclet onClean = null;

    @Override
    public void prepare(String jobId, Properties ctx, PipelineListener listener) {
        super.prepare(jobId,ctx,listener);
        if (onPrepare != null){
            LogicletContext logicletContext = new LogicletContext(ctx);
            try {
                XsObject doc = new JsonObject("root",new HashMap<String,Object>());
                onPrepare.execute(doc,doc, logicletContext, null);
                List<Pair<String,String>> env = new ArrayList<Pair<String,String>>();
                logicletContext.list(env);
                for (Pair<String,String> pair:env){
                    ctx.SetValue(pair.key(),pair.value());
                }
            }catch (Exception ex){
                LOG.info("Failed to prepare job:" + ExceptionUtils.getStackTrace(ex));
            }
        }
    }

    @Override
    public void configure(Properties p){
        super.configure(p);
    }

    @Override
    public void run(final String jobId, final Properties ctx, final PipelineListener listener) {
        if (script != null && theStep != null){
            try {
                final String stageRunId = PropertiesConstants.getString(ctx,"stageRunId","0");
                LogicletContext logicletContext = new LogicletContext(ctx);
                final List<StageContext> stageContexts = new ArrayList<StageContext>();

                StepContainer container = new StepContainer() {
                    protected int runId = 0;
                    @Override
                    public void newStep(String stepId,Properties stepCtx) {
                        String id = StringUtils.isEmpty(stepId)?newId():stepId;
                        StageContext stageContext = new StageContext(id,stepCtx,theStep);
                        stageContext.SetValue("runOrder",String.valueOf(runId++));
                        stageContext.SetValue("stepId",id);
                        theStep.prepare(jobId,stageContext,listener);
                        onCreate(jobId,stageRunId,stepId,stageContext);
                        stageContexts.add(stageContext);
                    }
                };

                XsObject doc = new JsonObject("root",new HashMap<String,Object>());
                try {
                    logicletContext.setObject(ID,container);
                    script.execute(doc, doc, logicletContext, null);
                }finally {
                    logicletContext.removeObject(ID);
                }

                boolean canceled = false;
                boolean allOk = true;
                for (StageContext stageContext:stageContexts){
                    if (isCanceled(jobId)){
                        canceled = true;
                        break;
                    }
                    String id = stageContext.getId();
                    PipelineStage stage = stageContext.getStage();
                    onStart(jobId,stageRunId,id,stageContext);
                    stage.run(jobId,stageContext,listener);
                    onFinish(jobId,stageRunId,id,stageContext);

                    String stageStage =
                            PropertiesConstants.getString(stageContext,"$state",
                                    "ok",true);
                    if (!stageStage.equals("ok") && !stage.isFaultTolerable()){
                        ctx.SetValue("$state","failed");
                        ctx.SetValue("$stateMessage", PropertiesConstants.getString(stageContext,"$stateMessage",
                                "failed",true));
                        allOk = false;
                        break;
                    }
                }

                if (canceled){
                    ctx.SetValue("$state", "canceled");
                    ctx.SetValue("$stateMessage", "canceled");
                }else {
                    if (allOk) {
                        ctx.SetValue("$state", "ok");
                        ctx.SetValue("$stateMessage", "ok");
                    }
                }
            }catch (Exception ex){
                ctx.SetValue("$state","failed");
                ctx.SetValue("$stateMessage",ex.getMessage());
            }finally {
                if (onClean != null){
                    LogicletContext logicletContext = new LogicletContext(ctx);
                    try {
                        XsObject doc = new JsonObject("root",new HashMap<String,Object>());
                        onClean.execute(doc,doc, logicletContext, null);
                    }catch (Exception ex){
                        LOG.info("Failed to clean step:" + ExceptionUtils.getStackTrace(ex));
                    }
                }
            }
        }
    }

    @Override
    public void configure(Element e, Properties p) {
        XmlElementProperties props = new XmlElementProperties(e,p);
        configure(props);
        loadPairs(e,p);

        Element elem = XmlTools.getFirstElementByPath(e, "script");
        if (elem != null){
            script = Script.create(elem, props);
        }

        elem = XmlTools.getFirstElementByPath(e, "on-prepare");
        if (elem != null){
            onPrepare = Script.create(elem, props);
        }

        elem = XmlTools.getFirstElementByPath(e, "on-clean");
        if (elem != null){
            onClean = Script.create(elem, props);
        }

        elem = XmlTools.getFirstElementByPath(e, "step");
        try {
            Factory<PipelineStage> f = new Factory<PipelineStage>();
            theStep = f.newInstance(elem,props,"module");
        }catch (Exception ex){
            LOG.error("Can not create pipeline stage:" + XmlTools.node2String(elem));
        }
    }

    public static interface StepContainer {
        public void newStep(String stepId,Properties stepCtx);
    }

    /**
     * 创建新的step
     */
    public static class NewStep extends Segment {
        protected String $id;

        public NewStep(String tag, Logiclet p) {
            super(tag, p);
        }

        @Override
        public void configure(Properties p){
            super.configure(p);
            $id = PropertiesConstants.getRaw(p,"id",$id);
        }

        @Override
        protected void onExecute(XsObject root,XsObject current, LogicletContext ctx,
                                 ExecuteWatcher watcher) {
            StepContainer container = ctx.getObject(ID);
            if (container != null){
                LogicletContext childCtx = new LogicletContext(ctx);
                super.onExecute(root, current, childCtx, watcher);
                container.newStep(PropertiesConstants.transform(childCtx, $id, ""), childCtx);
            }
        }
    }
}
